﻿#region Copyright Notice
// 
// 
//  
//  Copyright (c) 2009-2011 Anindya Chatterjee
//  
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//  
//      http://www.apache.org/licenses/LICENSE-2.0
//  
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// 
// 
#endregion
using System;
using System.Diagnostics;
using System.Threading;
using AbstractClass.NPerf.Framework;
using ThreadState = System.Threading.ThreadState;

namespace AbstractClass.NPerf
{
    /// <summary>
    /// Class used by Thread to run method. Each thread in test has its own class.
    /// </summary>
    internal class ThreadRun
    {
        private readonly CPUMonitor _cpuMonitor = new CPUMonitor();

        /// <summary>
        /// Indicates whether method executes minimum one second to collect performance counters correct.
        /// </summary>
        public bool CPUShortTime;

        /// <summary>
        /// Average cpu usage counter in test thread.
        /// </summary>
        public double CPUUsageCounter;

        /// <summary>
        /// Average performance counter value.
        /// </summary>
        public double CounterSample;

        /// <summary>
        /// Average duration time in test thread.
        /// </summary>
        public double[] Duration;

        /// <summary>
        /// Forwards exception message from executive thread to main unit testing loop thread.
        /// </summary>
        public Exception ExceptionFound;

        public long MemoryUsage;

        public NPerfTestRunner NPerfTestRunner;
        public bool ShortTime;

        /// <summary>
        /// Thread instance owner.
        /// </summary>
        public Thread Thread;

        public TestUnit Tu;

        private bool _jitWarmDone;

        /// <summary>
        /// Initializes class for one thread test.
        /// </summary>
        /// <param name="testRunner">This instance is needed to perform test run.</param>
        /// <param name="testUnit">This instance is used to inspect test properties.</param>
        public ThreadRun(NPerfTestRunner testRunner, TestUnit testUnit)
        {
            ExceptionFound = null;
            Tu = testUnit;
            NPerfTestRunner = testRunner;
            Duration = new double[testUnit.TestIteration];
        }

        /// <summary>
        /// Core thread function to execute test method.
        /// </summary>
        public void ThreadTest()
        {
            foreach (TestType testKind in Tu.TestTypes)
            {
                switch (testKind)
                {
                    case TestType.MemoryUsage:
                        {
                            try
                            {
                                JitWarmUp();
                                var memorizer = new MemoryMonitor();

                                for (int i = 0; i < Tu.TestIteration; i++)
                                {
                                    NPerfTestRunner.RunMethod(Tu.Fixture.StartUpLocalObjectType,
                                                              Tu.Fixture.StartUpLocalObjectMethod);

                                    // clean memory
                                    GC.Collect();
                                    GC.WaitForPendingFinalizers();
                                    GC.Collect();

                                    memorizer.Start();
                                    NPerfTestRunner.RunMethod(Tu.TestType, Tu.TestMethod);
                                    memorizer.Stop();

                                    MemoryUsage += memorizer.Usage;

                                    NPerfTestRunner.RunMethod(Tu.Fixture.TearDownLocalObjectType,
                                                              Tu.Fixture.TearDownLocalObjectMethod);
                                }
                            }
                            catch (Exception e)
                            {
                                ExceptionFound = e;
                                break;
                            }
                        }
                        break;
                    case TestType.HitCount:
                    case TestType.Duration:
                        {
                            try
                            {
                                JitWarmUp();
                                for (int i = 0; i < Tu.TestIteration; i++)
                                {
                                    NPerfTestRunner.RunMethod(Tu.Fixture.StartUpLocalObjectType,
                                                              Tu.Fixture.StartUpLocalObjectMethod);

                                    Duration[i] = TimeMonitor.Value;
                                    NPerfTestRunner.RunMethod(Tu.TestType, Tu.TestMethod);
                                    Duration[i] = (TimeMonitor.Value - Duration[i]) / TimeMonitor.Frequency;

                                    NPerfTestRunner.RunMethod(Tu.Fixture.TearDownLocalObjectType,
                                                              Tu.Fixture.TearDownLocalObjectMethod);
                                }
                            }
                            catch (Exception e)
                            {
                                ExceptionFound = e;
                                break;
                            }
                        }
                        break;
                    case TestType.CPUUsage:
                        {
                            CPUShortTime = true;
                            Thread t = null;
                            try
                            {
                                JitWarmUp();
                                //warm up monitor
                                Process process = Process.GetCurrentProcess();
                                _cpuMonitor.GetCPUUsage(process);
                                Thread.Sleep(300);

                                NPerfTestRunner.RunMethod(Tu.Fixture.StartUpLocalObjectType,
                                                          Tu.Fixture.StartUpLocalObjectMethod);

                                t = new Thread(CPUPerformanceThread);
                                t.Start(process);
                                NPerfTestRunner.RunMethod(Tu.TestType, Tu.TestMethod);
                                t.Abort();

                                NPerfTestRunner.RunMethod(Tu.Fixture.TearDownLocalObjectType,
                                                          Tu.Fixture.TearDownLocalObjectMethod);
                            }
                            catch (Exception e)
                            {
                                ExceptionFound = e;
                                break;
                            }
                            finally
                            {
                                if (t != null && t.ThreadState == ThreadState.Running)
                                    t.Abort();
                            }
                        }
                        break;
                    case TestType.None:
                        JitWarmUp();
                        for (int i = 0; i < Tu.TestIteration; i++)
                        {
                            Thread t = null;
                            try
                            {
                                NPerfTestRunner.RunMethod(Tu.Fixture.StartUpLocalObjectType,
                                                          Tu.Fixture.StartUpLocalObjectMethod);

                                if (Tu.IsGenericCounterUnit)
                                {
                                    t = new Thread(PerformanceThread);
                                    t.Start();
                                }

                                NPerfTestRunner.RunMethod(Tu.TestType, Tu.TestMethod);

                                if (Tu.IsGenericCounterUnit && t != null)
                                    t.Abort();

                                NPerfTestRunner.RunMethod(Tu.Fixture.TearDownLocalObjectType,
                                                          Tu.Fixture.TearDownLocalObjectMethod);
                            }
                            catch (Exception e)
                            {
                                ExceptionFound = e;
                                break;
                            }
                            finally
                            {
                                if (t != null && t.ThreadState == ThreadState.Running)
                                    t.Abort();
                            }
                        }
                        break;
                }
            }
        }

        /// <summary>
        /// This method working in separate thread collects performance counter.
        /// </summary>
        internal void CPUPerformanceThread(object state)
        {
            var process = state as Process;
            try
            {
                while (true)
                {
                    CPUUsageCounter = (CPUUsageCounter + _cpuMonitor.GetCPUUsage(process)) / 2;
                    CPUShortTime = false;
                    Thread.Sleep(300);
                }
            }
                // ReSharper disable EmptyGeneralCatchClause
            catch (Exception)
                // ReSharper restore EmptyGeneralCatchClause
            {
            }
        }

        internal void PerformanceThread()
        {
            var pc = new PerformanceCounter
                         {
                             CategoryName = Tu.CategoryName,
                             CounterName = Tu.CounterName,
                             InstanceName = Tu.InstanceName
                         };
            try
            {
                CounterSample = pc.NextValue();
                while (true)
                {
                    Thread.Sleep(1000);
                    CounterSample = (CounterSample + pc.NextValue()) / 2;
                    ShortTime = false;
                }
            }
                // ReSharper disable EmptyGeneralCatchClause
            catch (Exception)
                // ReSharper restore EmptyGeneralCatchClause
            {
            }
        }


        private void JitWarmUp()
        {
            //jitting for first run of the test
            if (!_jitWarmDone)
            {
                NPerfTestRunner.RunMethod(Tu.Fixture.StartUpLocalObjectType, Tu.Fixture.StartUpLocalObjectMethod);
                NPerfTestRunner.RunMethod(Tu.TestType, Tu.TestMethod);
                NPerfTestRunner.RunMethod(Tu.Fixture.TearDownLocalObjectType, Tu.Fixture.TearDownLocalObjectMethod);
                _jitWarmDone = true;
            }
        }
    }
}